# ManyToOneMetadataResolver.java

    package org.codefilarete.stalactite.engine.configurer.dslresolver;

    import java.lang.reflect.Field;
    import java.lang.reflect.ParameterizedType;
    import java.util.Collection;
    import java.util.Set;
    import java.util.function.Supplier;

    import org.codefilarete.reflection.AccessorByMethod;
    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.reflection.Accessors;
    import org.codefilarete.reflection.DefaultReadWritePropertyAccessPoint;
    import org.codefilarete.reflection.Mutator;
    import org.codefilarete.reflection.MutatorByMethod;
    import org.codefilarete.reflection.ReadWriteAccessPoint;
    import org.codefilarete.reflection.SerializableMutator;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
    import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.JoinColumnNamingStrategy;
    import org.codefilarete.stalactite.engine.configurer.NamingConfiguration;
    import org.codefilarete.stalactite.engine.configurer.dslresolver.InheritanceConfigurationResolver.ResolvedConfiguration;
    import org.codefilarete.stalactite.engine.configurer.dslresolver.MetadataSolvingCache.EntitySource;
    import org.codefilarete.stalactite.engine.configurer.manytomany.ManyToManyRelation;
    import org.codefilarete.stalactite.engine.configurer.manytoone.ManyToOneRelation;
    import org.codefilarete.stalactite.engine.configurer.manytoone.ManyToOneRelation.MappedByConfiguration;
    import org.codefilarete.stalactite.engine.configurer.model.DirectRelationJoin;
    import org.codefilarete.stalactite.engine.configurer.model.Entity;
    import org.codefilarete.stalactite.engine.configurer.model.IntermediaryRelationJoin;
    import org.codefilarete.stalactite.engine.configurer.model.ResolvedManyToManyRelation;
    import org.codefilarete.stalactite.engine.configurer.model.ResolvedManyToOneRelation;
    import org.codefilarete.stalactite.engine.runtime.AssociationTable;
    import org.codefilarete.stalactite.engine.runtime.IndexedAssociationTable;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Key;
    import org.codefilarete.stalactite.sql.ddl.structure.KeyMapping;
    import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.result.BeanRelationFixer;
    import org.codefilarete.tool.Nullable;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.bean.FieldIterator;
    import org.codefilarete.tool.bean.InstanceFieldIterator;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.KeepOrderSet;

    import static org.codefilarete.tool.Nullable.nullable;
    import static org.codefilarete.tool.collection.Iterables.first;

    /**
     * Resolves {@link ManyToManyRelation} DSL configurations into {@link ResolvedManyToManyRelation}
     * model instances. The process:
     * <ol>
     *   <li>Builds the target {@link EntitySource} via the inheritance resolver.</li>
     *   <li>Creates an {@link AssociationTable} (or {@link IndexedAssociationTable} when the relation is ordered).</li>
     *   <li>Wraps the table into an {@link IntermediaryRelationJoin}.</li>
     *   <li>Builds a {@link BeanRelationFixer} that populates both the source collection and, when the mapping is
     *       bidirectional, the reverse collection on the target side.</li>
     *   <li>Registers the resolved relation on the source {@link Entity}.</li>
     * </ol>
     *
     * @author Guillaume Mary
     */
    public class ManyToOneMetadataResolver {

    	private final Dialect dialect;
    	private final ConnectionConfiguration connectionConfiguration;

    	public ManyToOneMetadataResolver(Dialect dialect, ConnectionConfiguration connectionConfiguration) {
    		this.dialect = dialect;
    		this.connectionConfiguration = connectionConfiguration;
    	}

    	/**
    	 * Entry point: resolves all many-to-many relations (direct and inset-embedded) declared within the given source.
    	 *
    	 * @return the set of target {@link EntitySource}s produced by the resolved relations, to be enqueued for further traversal
    	 */
    	<C, I> Set<EntitySource<?, ?>> resolve(EntitySource<C, I> source) {
    		KeepOrderSet<EntitySource<?, ?>> targetEntities = new KeepOrderSet<>();
    		// configuring many-to-manys owned by this entity
    		source.getResolvedConfigurations().forEach(resolvedConfiguration -> {
    			targetEntities.addAll(resolve(source.getEntity(), resolvedConfiguration.getMappingConfiguration()));
    		});
    		return targetEntities;
    	}

    	private <C, I> Set<EntitySource<?, ?>> resolve(Entity<C, I, ?> entity, EntityMappingConfiguration<C, I> mappingConfiguration) {
    		KeepOrderSet<EntitySource<?, ?>> targetEntities = new KeepOrderSet<>();
    		mappingConfiguration.getManyToOnes().forEach(manyToOne -> {
    			EntitySource<Object, Object> resolved = this.resolve(entity, manyToOne);
    			targetEntities.add(resolved);
    		});
    		// treating relations embedded in insets
    		mappingConfiguration.getPropertiesMapping().getInsets().forEach(inset -> {
    			inset.getConfigurationProvider().getConfiguration().getManyToOnes().forEach(manyToOne -> {
    				EntitySource<Object, Object> resolved = this.resolve(entity, manyToOne.embedInto(inset.getAccessor()));
    				targetEntities.add(resolved);
    			});
    		});
    		return targetEntities;
    	}

    	/**
    	 * Resolves a single many-to-one relation.
    	 *
    	 * @return the target {@link EntitySource}, ready to be enqueued for further (recursive) resolution
    	 */
    	<SRC, TRGT, S extends Collection<SRC>, SRCID, TRGTID, SRCTABLE extends Table<SRCTABLE>, TRGTTABLE extends Table<TRGTTABLE>>
    	EntitySource<TRGT, TRGTID> resolve(Entity<SRC, SRCID, SRCTABLE> source, ManyToOneRelation<SRC, TRGT, TRGTID, S> manyToOne) {

    		EntitySource<TRGT, TRGTID> targetEntitySource = buildTargetEntity(manyToOne);
    		NamingConfiguration namingConfiguration = first(targetEntitySource.getResolvedConfigurations()).getNamingConfiguration();
    		Entity<TRGT, TRGTID, TRGTTABLE> targetEntity = targetEntitySource.getEntity();

    		PrimaryKey<TRGTTABLE, TRGTID> rightPrimaryKey = targetEntity.getTable().getPrimaryKey();

    		ForeignKeyNamingStrategy foreignKeyNamingStrategy = namingConfiguration.getForeignKeyNamingStrategy();

    		KeyMapping<SRCTABLE, TRGTTABLE, TRGTID> foreignKey = determineForeignKeyColumns(manyToOne, rightPrimaryKey, source.getTable(), namingConfiguration.getJoinColumnNamingStrategy(), foreignKeyNamingStrategy);

    		DirectRelationJoin<SRCTABLE, TRGTTABLE, TRGTID> join = new DirectRelationJoin<>(foreignKey, foreignKey.getReferencedKey());

    		BeanRelationFixer<SRC, TRGT> relationFixer = determineRelationFixer(manyToOne, source.getEntityType(), targetEntity.getEntityType());

    		ResolvedManyToOneRelation<SRC, TRGT, TRGTID, SRCTABLE, TRGTTABLE> entitiesLink =
    				new ResolvedManyToOneRelation<>(
    						targetEntity,
    						manyToOne.getTargetProvider(),
    						manyToOne.getRelationMode(),
    						manyToOne.isFetchSeparately(),
    						join,
    						relationFixer,
    						manyToOne.isNullable()
    				);
    		source.addRelation(entitiesLink);

    		return targetEntitySource;
    	}

    	/**
    	 * Builds the target {@link EntitySource} by resolving the full inheritance hierarchy of the target entity.
    	 */
    	private <SRC, TRGT, TRGTID, S extends Collection<SRC>> EntitySource<TRGT, TRGTID>
    	buildTargetEntity(ManyToOneRelation<SRC, TRGT, TRGTID, S> manyToOne) {
    		InheritanceConfigurationResolver<TRGT, TRGTID> inheritanceConfigurationResolver = new InheritanceConfigurationResolver<>();
    		KeepOrderSet<ResolvedConfiguration<?, TRGTID>> ancestorsConfigurations =
    				inheritanceConfigurationResolver.resolveConfigurations(manyToOne.getTargetMappingConfiguration());

    		InheritanceMetadataResolver<TRGT, TRGTID, ?> inheritanceMetadataResolver = new InheritanceMetadataResolver<>(dialect, connectionConfiguration);
    		return inheritanceMetadataResolver.resolve(ancestorsConfigurations);
    	}

    	protected <SRC, TRGT, TRGTID, S extends Collection<SRC>, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>>
    	KeyMapping<LEFTTABLE, RIGHTTABLE, TRGTID> determineForeignKeyColumns(ManyToOneRelation<SRC, TRGT, TRGTID, S> manyToOne,
    	                                                                     PrimaryKey<RIGHTTABLE, TRGTID> rightPrimaryKey,
    	                                                                     LEFTTABLE leftTable,
    	                                                                     JoinColumnNamingStrategy joinColumnNamingStrategy,
    	                                                                     ForeignKeyNamingStrategy foreignKeyNamingStrategy) {

    		Column<LEFTTABLE, TRGTID> providedColumn = (Column<LEFTTABLE, TRGTID>) manyToOne.getOwningColumn();
    		// small check for incongruous reverse column definition: it can't be possible when key is composite
    		if (providedColumn != null && rightPrimaryKey.isComposed()) {
    			throw new UnsupportedOperationException("Can't map composite primary key " + rightPrimaryKey + " on single foreign key : " + providedColumn);
    		}

    		// priority 1: take user definition of the owning column
    		Key.KeyBuilder<LEFTTABLE, TRGTID> leftKeyBuilder = Key.from(leftTable);
    		if (providedColumn == null) {
    			String reverseColumnName = manyToOne.getColumnName();
    			if (reverseColumnName != null) {
    				Column<RIGHTTABLE, TRGTID> rightPKColumn = (Column<RIGHTTABLE, TRGTID>) first(rightPrimaryKey.getColumns());
    				providedColumn = leftTable.addColumn(reverseColumnName, rightPKColumn.getJavaType());
    				leftKeyBuilder.addColumn(providedColumn);
    			}
    		} else {
    			leftKeyBuilder.addColumn(providedColumn);
    		}

    		// priority 2: user didn't define the owning column, but we can guess it from the target accessor
    		if (providedColumn == null) {
    			AccessorDefinition accessorDefinition = AccessorDefinition.giveDefinition(manyToOne.getTargetProvider());
    			rightPrimaryKey.getColumns().forEach(pkColumn -> {
    				String effectiveLeftColumnName = joinColumnNamingStrategy.giveName(accessorDefinition, pkColumn);
    				Column<LEFTTABLE, ?> column = leftTable.addColumn(effectiveLeftColumnName, pkColumn.getJavaType());
    				leftKeyBuilder.addColumn(column);
    			});
    		}

    		Key<LEFTTABLE, TRGTID> leftKey = leftKeyBuilder.build();

    		// According to the nullable option, we specify the ddl schema option
    		leftKey.getColumns().forEach(c -> ((Column) c).nullable(manyToOne.isNullable()));

    		if (manyToOne.isTargetTablePerClassPolymorphic()) {
    			// We don't create FK for table-per-class polymorphism because source columns would reference different tables
    			// (one per concrete entity type) which databases do not allow
    			return new KeyMapping<>(leftKey, rightPrimaryKey);
    		} else {
    			String foreignKeyName = foreignKeyNamingStrategy.giveName(leftKey, rightPrimaryKey);
    			return leftTable.addForeignKey(foreignKeyName, leftKey, rightPrimaryKey);
    		}
    	}

    	protected <SRC, TRGT, S extends Collection<SRC>>
    	BeanRelationFixer<SRC, TRGT> determineRelationFixer(ManyToOneRelation<SRC, TRGT, ?, S> manyToOneRelation, Class<SRC> sourceEntityType, Class<TRGT> targetEntityType) {
    		Mutator<SRC, TRGT> targetSetter = manyToOneRelation.getTargetProvider();
    		SerializableMutator<TRGT, SRC> reverseCombiner = buildReverseCombiner(manyToOneRelation, sourceEntityType, targetEntityType);

    		if (reverseCombiner == null) {
    			return BeanRelationFixer.of(targetSetter);
    		} else {
    			return (target, input) -> {
    				targetSetter.set(target, input);
    				reverseCombiner.set(input, target);
    			};
    		}
    	}

    	/**
    	 * Build the combiner between target entities and source ones.
    	 *
    	 * @param targetEntityType target entity type, provided to look up for reverse property if no sufficient info was given
    	 * @return null if no information was provided about the reverse side (no bidirectionality)
    	 */
    	private <SRC, TRGT, S extends Collection<SRC>>
    	SerializableMutator<TRGT, SRC> buildReverseCombiner(ManyToOneRelation<SRC, TRGT, ?, S> manyToOneRelation, Class<SRC> sourceEntityType, Class<TRGT> targetEntityType) {
    		MappedByConfiguration<SRC, TRGT, S> mappedByConfiguration = manyToOneRelation.getMappedByConfiguration();
    		if (mappedByConfiguration.isEmpty()) {
    			// relation is not bidirectional, and not even set by the reverse link, there's nothing to do
    			return null;
    		} else {
    			ReadWriteAccessPoint<TRGT, S> collectionAccessor = manyToOneRelation.buildReversePropertyAccessor();
    			if (collectionAccessor == null) {
    				// since some reverse info has been done but not the collection accessor, we try to find the matching property by type
    				FieldIterator targetFields = new InstanceFieldIterator(targetEntityType);
    				Field reverseField = Iterables.find(targetFields, field -> Collection.class.isAssignableFrom(field.getType())
    						&& ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].equals(sourceEntityType));
    				if (reverseField != null) {
    					Nullable<AccessorByMethod<TRGT, S>> reverseGetterMethod = nullable(Accessors.accessorByMethod(reverseField));
    					if (reverseGetterMethod.isPresent()) {
    						collectionAccessor = new DefaultReadWritePropertyAccessPoint<>(reverseGetterMethod.get());
    					} else {
    						Nullable<MutatorByMethod<TRGT, S>> reverseSetterMethod = nullable(Accessors.mutatorByMethod(reverseField));
    						if (reverseSetterMethod.isPresent()) {
    							collectionAccessor = new DefaultReadWritePropertyAccessPoint<>(reverseSetterMethod.get());
    						}
    					}
    				} // else : relation is not bidirectional, or not a usual one, may be set by reverse link
    			}

    			Nullable<SerializableMutator<TRGT, SRC>> configuredCombiner = nullable(mappedByConfiguration.getCombiner());
    			if (collectionAccessor == null) {
    				return configuredCombiner.get();
    			} else {
    				// collection factory is in priority the one configured
    				Supplier<S> collectionFactory = mappedByConfiguration.getFactory();
    				if (collectionFactory == null) {
    					Class<S> collectionType = AccessorDefinition.giveDefinition(collectionAccessor).getMemberType();
    					collectionFactory = Reflections.giveCollectionFactory(collectionType);
    				}
    				ReadWriteAccessPoint<TRGT, S> finalCollectionAccessor = collectionAccessor;
    				SerializableMutator<TRGT, SRC> combiner = configuredCombiner.getOr((TRGT trgt, SRC src) -> {
    					// collectionAccessor can't be null due to nullable check
    					finalCollectionAccessor.get(trgt).add(src);
    				});

    				Supplier<S> effectiveCollectionFactory = collectionFactory;
    				return (TRGT trgt, SRC src) -> {
    					// we call the collection factory to ensure that property is initialized
    					if (finalCollectionAccessor.get(trgt) == null) {
    						finalCollectionAccessor.set(trgt, effectiveCollectionFactory.get());
    					}
    					// Note that combiner can't be null here thanks to nullable(..) check
    					combiner.set(trgt, src);
    				};
    			}
    		}
    	}
    }